//**************************************************************
//					新人研修用ソースプログラム	
//**************************************************************
//ファイル名：practice13.c						
//**************************************************************

//===========================
//インクルード
//===========================
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//===========================
//マクロ
//===========================
#define SIZE 100

//===========================
//構造体定義
//===========================
typedef struct LIST{
	char* name;			// 名前データ
	char* address;		// 住所データ
	char* age;			// 年齢データ
	struct LIST* next;  // 次の要素へのポインタ
	struct LIST* prev;  // 前の要素へのポインタ
}address;

//===========================
//プロトタイプ宣言
//===========================
// ★★ ローカル関数はstaticにしておく事(外部からアクセス可能にしない。)
//   → staticにする意図については
//        http://www.geocities.co.jp/SiliconValley/6071/technic/39.html
//      を参照
//   → staticにするかどうかは自己判断にはなるが、機能の明確化、隠蔽化と
//      言う意味でも意識して設計する事
static void newline(void);
static int add_list(address*, char);
static int delete_list(address*, char);
static int show_list(address*, char);
static address* set_tailpointer(address*);
static int free_all(address*);

//*********************************************************************
//機能名称：メイン関数
//関数名　：int main()
//引数　　：なし
//戻り値　：常に0
//機能　　：メニュー画面を表示して数値入力を促す。その数値から利用する
//　　　　：機能の関数を呼び出す。
//*********************************************************************	
int main(void){
	
	char ret = 0;	
	address head;		// 先頭ダミーリスト宣言
	char selected = '\0';
	head.next = &head;	// 先頭ダミーの次に先頭ダミー自体をつなぐ
	head.prev = &head;	// 先頭ダミーの前に先頭ダミー自体をつなぐ
	
	do{
		printf("メニューを選択してください。\n\n");
		printf("アドレス帳の先頭にデータを追加：1\n");
		printf("アドレス帳の末尾にデータを追加：2\n");
		printf("アドレス帳データの名前検索表示：3\n");
		printf("アドレス帳データの名前検索削除：4\n");
		printf("アドレス帳の先頭データ削除    ：5\n");
		printf("アドレス帳の末尾データ削除    ：6\n");
		printf("アドレス帳の全データ表示      ：7\n");
		printf("終了：q\n");
		
		selected = getchar();
		if(selected != '\0'){
			newline();
		}

		switch(selected){
		 /* 先頭に追加 */	
		 case '1':
		 case '2':
		 	// ★★
		 	//  ret = add_list(&head, selected);
		 	// で結果を見るようにする方がエラー処理をきちんと行える。
			ret = add_list(&head, selected);
			break;
		 /* 表示 */
		 case '3':
		 case '7':
		 	// ★★
		 	//  ret = show_list(&head, selected);
		 	// で結果を見るようにする方がエラー処理をきちんと行える。
			ret = show_list(&head, selected);
			break;
		 /* 削除 */  
		 case '4':
		 case '5':
		 case '6': 
		 	// ★★
		 	//  ret = delete_list(&head, selected);
		 	// で結果を見るようにする方がエラー処理をきちんと行える。
			ret = delete_list(&head, selected);
			break;
		 /* 終了 */
		 case 'q':   
			break;
		 /* 無効 */
		 default:    
			printf("\n入力値が正しくありません\n");
			break;
		}
		// ★★
		// if(ret != 0)
		// {
		//   ～～～
		// }
		// とかでエラー処理した方がシステムとして優良。
		/* 各関数のエラー終了時の処理 */
		if(ret != 0){
			printf("\n関数からの戻り値にエラーがあります。\nシステムを終了します。\n");
			break;
		}
	}while(selected != 'q');
	
	// ★★ ここはアウト！！！！
	// 追加したデータの開放漏れ(全部メモリリークしてます)
	if(&head != NULL){
		ret = free_all(&head);
	}
	
	return 0;
}

//*********************************************************************
//機能名称：新規データ追加関数
//
//関数名：static int add_list()
//引数　：address* add_head, char selected_num
//戻り値：0:正常終了
//　　　：-1:エラー
//機能　：リストの先頭、または末尾に新規データを追加する。
//*********************************************************************	
// ★★ ローカル関数はstaticにしておく事(外部からアクセス可能にしない。)
// ★★ voidだと成功かエラーか、またエラーの場合は何のエラーが起こったか、
//      呼び出し元で判断できない。
//      関数の機能内容にもよるが、データの追加/削除と言った機能の核となる
//      モジュールなのであれば、intやshort型にして明示的に結果を返す方が親切。
static int add_list(address* add_head, char selected_num){
	
	address* p = NULL;
	address* newdata = NULL;
	int age = 0;				// 年齢格納用変数
	char temp[SIZE] = {'\0'};	// メモリ確保作業用文字列
	char er_i = 0;
	char break_flg = 0;

	// ★★! ヌルポチェック漏れ：add_head
	// もしadd_headがNULLだったらぶっ飛んでまう
	/* add_headヌルポインタチェック */
	if(add_head == NULL){
		printf("\nNULLポインタを参照しました\n");
		return -1;
	}
	// ★★ キャストすること(addressへのポインタ)
	/* 先頭要素の次の要素のアドレス */
	p = add_head->next;

	/* リストが空かどうか(ダミーしかない場合はp == NULL) */
	if(p != NULL){
		/* 先頭要素に戻ったら終了 */
		while(p != add_head){
			/* 次の要素へ進める */
			p = p->next;
		}
	}
	
	/* 末尾に追加する場合はポインタをずらす */
	if(selected_num == '2'){
		p = set_tailpointer(p);
	}

	/* 先頭に追加する要素用のメモリ領域確保 */
	newdata = (address*)malloc(sizeof(address));
	/* 領域確保失敗 */
	if(newdata == NULL){
		// ★★ Error内容を明確にすること
		printf("\nデータ追加領域を確保できませんでした。\n");
		// ★★ exitは使用しない方が良い(システムの強制終了なので。組み込みのプログラムで使用する事はほぼ無い)
		return -1;
	}
	
	/* 新しい要素にデータを設定 */
	printf("データを追加します。\n");
	printf("\n名前を入力して下さい(スペースなし)：");
	fgets(temp, sizeof(temp), stdin);
	newdata->name = (char*)malloc(strlen(temp) + 1);
	
	if(newdata->name == NULL){
		printf("\n名前登録用の領域確保に失敗しました\n");
		// ★★ exitは使用しない方が良い(システムの強制終了なので。組み込みのプログラムで使用する事はほぼ無い)
		return -1;
	}
	strcpy(newdata->name, temp);
	printf("%s\n", newdata->name);

	printf("\n住所を入力してください(スペースなし)：");
	fgets(temp, sizeof(temp), stdin);
	newdata->address = (char*)malloc(strlen(temp) + 1);
	if(newdata->address == NULL){
		printf("\n住所登録用の領域確保に失敗しました\n");
		// ★★ exitは使用しない方が良い(システムの強制終了なので。組み込みのプログラムで使用する事はほぼ無い)
		return -1;
	}
	/* ライブラリ関数使用 */
	strcpy(newdata->address, temp);
	printf("%s\n", newdata->address);
	
	while(1){
		/* 入力値エラーチェック */
		printf("\n年齢を入力してください：");
		fgets(temp, sizeof(temp), stdin);
		/* [0]に数字文字以外が入っているとき */
		if(temp[0] < '0' || temp[0] > '9'){
			printf("\n数字を入力してください\n");
			continue;
		}
		else if(temp[0] == '0'){
			/* 0歳の場合 */
			if(temp[1] == '\n'){
				break;
			}
			else{
				printf("\n先頭に0が付いています\n");
				continue;
			}
		}
		else{
			/* 文字列の終わりまで全て数字文字かどうか調べる */
			break_flg = 0;
			er_i = 1;
			while(temp[er_i] != '\n'){
				if(temp[er_i] < '0' || temp[er_i] > '9'){
					break_flg = 1;
				}
				er_i++;
			}
			if(break_flg == 0){
				break;
			}
			else{
				printf("\n数字のみを入力してください\n");
				continue;
			}
		}
	}
	
	newdata->age = (char*)malloc(strlen(temp) + 1);
	if(newdata->age == NULL){
		printf("\n年齢登録用の領域確保に失敗しました\n");
		// ★★ exitは使用しない方が良い(システムの強制終了なので。組み込みのプログラムで使用する事はほぼ無い)
		return -1;
	}
	// ライブラリ関数使用
	strcpy(newdata->age, temp);
	printf("%s\n", newdata->age);
	/* 新しい要素と次の要素をつなぐ */
	newdata->next = p->next;
	/* 新しい要素と前の要素をつなぐ */
	newdata->prev = p;
	/* 新しい要素の直後の要素のprevに、新しい要素を設定する */
	p->next->prev = newdata;
	/* 新しい要素の直前の要素のnextに、新しい要素のアドレスを設定 */
	p->next = newdata;
	
	return 0;
}

//*********************************************************************
//機能名称：データ削除関数
//
//関数名　：satatic int delete_list()
//引数　　：address* del_head, char selected_num
//戻り値　：0:正常終了
//　　　　：-1:エラー
//機能　　：リスト先頭、または末尾のデータ削除。
//　　　　：入力された名前に一致したデータの削除。
//*********************************************************************	
// ★★ add_listと同様
static int delete_list(address* del_head, char selected_num){

	address* p = NULL;
	address* memorize_p = NULL;
	char temp[SIZE] = {'\0'};			// メモリ確保作業用文字列

	// ★★ ヌルポチェック漏れ：del_head
	// もしdel_headがNULLだったらぶっ飛んでまう
	/* del_headヌルポインタチェック */
	if(del_head == NULL){
		printf("\nNULLポインタを参照しました\n");
		return -1;
	}

	/* データがない場合、削除処理はしない */
	if(del_head->next == del_head){
		printf("\nデータが1件も登録されていません。\n");
		return -1;
	}
	/* 先頭要素の次の要素のアドレス */
	p = del_head->next;
	/* リストが空かどうか(ダミーしかない場合はp == NULL) */
	if(p != NULL){
		/* 先頭要素のひとつ次へ戻ったら終了 */
		while(p->prev != del_head){
			/* 次の要素へ進む */
			p = p->next;
		}
	}
	/* 検索削除 */
	if(selected_num == '4'){
		printf("\n削除する名前を入力して下さい(スペースなし):");
		fgets(temp, sizeof(temp), stdin);
		while(p != del_head){
			/* 一致する値がないままリストの末尾まで来た */
			if(p == del_head){
				printf("\n指定された要素はリスト内に存在しません\n");
			}
			else if((strcmp(p->name, temp)) == 0){
				p->prev->next = p->next;
				p->next->prev = p->prev;
				memorize_p = p->next;
				if(p != NULL){
					free(p);
				}
				else{
					printf("\nNULLポインタを解放しようとしました\n");
					return -1;
				}
				p = memorize_p;
			}
			else{
				p = p->next;
			}
		}
	}
	else{
		/* 末尾を削除する場合はポインタをずらす */
		if(selected_num == '6'){
			p = set_tailpointer(p);
			p = set_tailpointer(p);
		}
		if(p == NULL){
			printf("\nNULLポインタを解放しようとしました\n");
			return -1;
		}
		/* 削除する要素の直後の要素と直前の要素をつなぐ */
		p->prev->next = p->next;
		/* 削除する要素の直前の要素と直後の要素をつなぐ */
		p->next->prev = p->prev;
		
		/* 削除対象要素をfreeで解放して削除する */
		// ★★ pがNULLだったらぶっ飛ぶ。要チェック！！！
		free(p);
	}
	return 0;
}

//*********************************************************************
//機能名称：データ表示関数
//
//関数名　：static int show_list()
//引数　　：address* show_head, char selected_num
//戻り値　：0:正常終了
//　　　　：-1:エラー
//機能　　：リスト先頭からの全データ表示。
//　　　　：入力された名前に一致したデータの表示。
//*********************************************************************	
// ★★ add_listと同様
static int show_list(address* show_head, char selected_num){
	
	address* p;
	int data;
	char temp[SIZE] = {'\0'};

	// ★★ ヌルポチェック漏れ
	/* show_headヌルポインタチェック */
	if(show_head == NULL){
		printf("\nNULLポインタを参照しました\n");
		return -1;
	}
	
	/* 検索表示用 */
	if(selected_num == '3'){
		/* 検索データ入力 */
		printf("表示する名前を入力して下さい(スペースなし):");
		fgets(temp, sizeof(temp), stdin);
	}
	for(p = show_head->next; p != show_head; p = p->next){
		/* 検索表示 */
		if(selected_num == '3'){
			/* 一致する値がないままリストの末尾まで来た */
			if(p == show_head){
				printf("\n指定された要素はリスト内に存在しません\n");
			}
			else if((strcmp(p->name, temp)) == 0){
				printf("名前：%s\n", p->name);
				printf("住所：%s\n", p->address);
				printf("年齢：%s\n\n", p->age);
			}
		}
		/* 全件表示 */
		else{
			printf("名前：%s\n", p->name);
			printf("住所：%s\n", p->address);
			printf("年齢：%s\n\n", p->age);
		}
	}
	return 0;
}

//*********************************************************************
//機能名称　：ポインタ移動関数
//
//関数名　　：void set_tailpointer(void)
//引数　　　：address_* set_pointer
//戻り値　　：address-* set_pointer->prev;
//機能　　　：循環リストの末尾にデータ追加･削除処理をする場合に、
//　　　　　：作業用ポインタをずらす関数。
//*********************************************************************	
address* set_tailpointer(address* set_pointer){
	return set_pointer->prev;
}

//*********************************************************************
//機能名称　：改行文字削除関数
//
//関数名　　：void newline(void)
//引数　　　：なし
//戻り値　　：なし
//機能　　　：fgets関数によって格納される意図しない改行文字を削除する。
//*********************************************************************	
void newline(void){
	int c;
	while ((c = getchar()) != EOF){
		if (c == '\n'){
			return;
		}
	}
}

//*********************************************************************
//機能名称：メモリ解放関数
//関数名　：static int free_all()
//引数　　：address* free_head
//戻り値　：0:
//　　　　：-1:エラー
//機能　　：システム終了前にリスト先頭から動的確保したメモリ領域を
//　　　　：解放する。
//*********************************************************************	
// ★★ add_listと同様
static int free_all(address* free_head){
	
	address* p;
	free_head = NULL;
	
	/* free_headヌルポインタチェック */
	if(free_head == NULL){
		printf("終了します");
		return -1;
	}
	for(p = free_head->next; p != free_head; p = p->next){
		if(p == NULL){
			printf("\nNULLポインタを解放しようとしました\n");
			return -1;
		}
		/* 削除する要素の直後の要素と直前の要素をつなぐ */
		p->prev->next = p->next;
		/* 削除する要素の直前の要素と直後の要素をつなぐ */
		p->next->prev = p->prev;
		/* 削除対象要素をfreeで解放して削除する */
		free(p);
	}
	printf("全メモリを解放しました");
	return 0;
}
